package com.smart4j.framework.util;

import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import lombok.extern.slf4j.Slf4j;

/**
 * 类操作工具
 *
 * @author 段誉
 * @create 2019-03-28 19:01
 */
@Slf4j
public final class ClassUtil {
  /**
   * 获取类加载器
   * 获取当前线程中的ClassLoader
   */
  public static ClassLoader getClassLoader() {
    return Thread.currentThread().getContextClassLoader();
  }

  /**
   * 加载类
   * 为了提高性能可以将isInitialized设置为false
   * @param className 类名
   * @param isInitialized 是否初始化（指是否执行类的静态代码块）
   * @return
   */
  public static Class<?> loadClass(String className, boolean isInitialized) {
    Class<?> cls;
    try {
      cls = Class.forName(className, isInitialized, getClassLoader());
    } catch (ClassNotFoundException e) {
      log.error("load class failure", e);
      throw new RuntimeException(e);
    }
    return cls;
  }

  /**
   * 获取指定包名下的所有类
   * 需要根据包名并将其转换位文件路径，读取class文件或jar包，获取指定的类名去加载类
   * @param packageName 包名
   * @return
   */
  public static Set<Class<?>> getClassSet(String packageName) {
    Set<Class<?>> classSet = new HashSet<>();
    try {
      Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".", "/"));
      while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        if (url == null) {
          continue;
        }

        String protocol = url.getProtocol();
        if (protocol.equals("file")) {
          String packagePath = url.getPath().replaceAll("%20", " ");
          addClass(classSet, packagePath, packageName);
        } else if (protocol.equals("jar")) {
          JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
          if (jarURLConnection == null) {
            continue;
          }

          JarFile jarFile = jarURLConnection.getJarFile();
          if (jarFile == null) {
            continue;
          }
          Enumeration<JarEntry> jarEntries = jarFile.entries();
          while (jarEntries.hasMoreElements()) {
            JarEntry jarEntry = jarEntries.nextElement();
            String jarEntryName = jarEntry.getName();
            //endswith用于检测字符串是否以指定的后缀结束
            if (jarEntryName.endsWith(".class")) {
              //lastIndexOf返回指定字符在此字符串中最后一次出现处的索引
              String className = jarEntryName
                      .substring(0, jarEntryName.lastIndexOf("."))
                      .replaceAll("/", ".");
              doAddClass(classSet, className);
            }
          }
        }
      }
    } catch (IOException e) {
      log.error("get class set failure", e);
      throw new RuntimeException(e);
    }
    return classSet;
  }

  /**
   * 拼接好类路径
   * @param classSet 类集合
   * @param packagePath 包路径
   * @param packageName 包名
   */
  private static void addClass(Set<Class<?>> classSet, String packagePath, String packageName) {
    //拿到包下边所有class文件/文件夹
    File[] files = new File(packagePath).listFiles(file -> file.isDirectory()
            || (file.isFile() && file.getName().endsWith(".class")));
    for (File file: files) {
      String fileName = file.getName();
      //是文件则拼接好路径直接添加
      if (file.isFile()) {
        String className = fileName.substring(0, fileName.lastIndexOf("."));
        if (StringUtils.isNotEmpty(packageName)) {
          className = packageName + "." + className;
        }
        doAddClass(classSet, className);
      } else {
        //如果是文件夹类型则将文件名拼接到后边进行递归查询
        String subPackagePath = fileName;
        if (StringUtils.isNotEmpty(packagePath)) {
          subPackagePath = packagePath + "/" + subPackagePath;
        }
        String subPackageName = fileName;
        if (StringUtils.isNotEmpty(packageName)) {
          subPackageName = packageName + "." + subPackageName;
        }
        addClass(classSet, subPackagePath, subPackageName);
      }
    }
  }

  private static void doAddClass(Set<Class<?>> classSet, String className) {
    Class<?> cls = loadClass(className, false);
    classSet.add(cls);
  }
}
